using System;
using GameFrame.Deterministic;

namespace GameFrame
{
    public struct ComponentFilter<T0> where T0 : unmanaged, IComponent
    {
        private bool _anySuccess;
        private bool _withoutSuccess;
        private bool _cullingSuccess;
        private FrameBase _registry;
        private ComponentSet _any;
        private ComponentSet _with;
        private ComponentSet _without;
        private readonly unsafe ComponentDataBuffer* _t0;
        private ComponentBlockIterator _blockIterator;
        private int _t0Id;
        private int _blockIndex;
        private int _blockCount;
        private unsafe byte* _blockComponentData;
        private unsafe EntityRef* _blockEntityHandles;

        public bool UseCulling
        {
            get => !this._cullingSuccess;
            set => this._cullingSuccess = !value;
        }

        internal unsafe ComponentFilter(
            FrameBase f,
            ComponentSet with,
            ComponentSet any,
            ComponentSet without)
        {
            this._registry = f;
            this._t0 = this._registry.GetDataBuffer<T0>();
            this._any = any;
            this._with = with;
            this._without = without;
            this._anySuccess = any.IsEmpty;
            this._withoutSuccess = without.IsEmpty;
            this._cullingSuccess = f.IsVerified;
            this._blockIterator = new ComponentBlockIterator(this._t0);
            this._blockIndex = 0;
            this._blockCount = 0;
            this._t0Id = ComponentTypeId<T0>.Id;
            this._blockComponentData = (byte*)null;
            this._blockEntityHandles = (EntityRef*)null;
        }

        public unsafe bool Next(out EntityRef entity, out T0 component0)
        {
            T0* component0_1;
            if (this.NextUnsafe(out entity, out component0_1))
            {
                component0 = *component0_1;
                return true;
            }

            component0 = default(T0);
            return false;
        }

        public unsafe bool NextUnsafe(out EntityRef entity, out T0* component0)
        {
            Assert.Check(true);
            while (true)
            {
                if ((IntPtr)this._blockComponentData == IntPtr.Zero)
                {
                    if (this._blockIterator.NextBlock(out this._blockComponentData, out this._blockEntityHandles,
                            out this._blockCount) && this._blockCount != 0)
                        this._blockIndex = 0;
                    else
                        break;
                }

                Assert.Check(this._blockCount > 0);
                for (; this._blockIndex < this._blockCount; ++this._blockIndex)
                {
                    EntityRef entityRef = this._blockEntityHandles[this._blockIndex];
                    ComponentSet componentSet = this._registry._componentMasks[entityRef.Index];
                    // ISSUE: reference to a compiler-generated field
                    // ISSUE: reference to a compiler-generated field
                    // ISSUE: reference to a compiler-generated field
                    // ISSUE: reference to a compiler-generated field
                    if ((uint)entityRef.Index < (uint)this._registry._capacity &&
                        this._registry._info[entityRef.Index].Ref.Version == entityRef.Version &&
                        componentSet.IsSet(this._t0Id) &&
                        (this._anySuccess ||
                         ((long)componentSet._set.FixedElementField & (long)this._any._set.FixedElementField) != 0L ||
                         ((long)componentSet._set[1] & (long)this._any._set[1]) != 0L ||
                         ((long)componentSet._set[2] & (long)this._any._set[2]) != 0L ||
                         ((long)componentSet._set[3] & (long)this._any._set[3]) != 0L) && (this._withoutSuccess ||
                            ((long)componentSet._set.FixedElementField & (long)this._without._set.FixedElementField) ==
                            0L && ((long)componentSet._set[1] & (long)this._without._set[1]) == 0L &&
                            ((long)componentSet._set[2] & (long)this._without._set[2]) == 0L &&
                            ((long)componentSet._set[3] & (long)this._without._set[3]) == 0L) &&
                        (this._cullingSuccess || !this._registry.IsCulled(entityRef)))
                    {
                        entity = entityRef;
                        Assert.Check(sizeof(T0) == this._t0->_stride);
                        this._t0->AssertHandleVersion(entityRef);
                        int num = (int)this._t0->_sparse[entityRef.Index];
                        component0 = (T0*)(this._t0->_blocksList[num / this._t0->_blockCapacity].PackedData +
                                           sizeof(T0) * (num % this._t0->_blockCapacity));
                        ++this._blockIndex;
                        return true;
                    }
                }

                this._blockComponentData = (byte*)null;
            }

            entity = new EntityRef();
            component0 = (T0*)null;
            return false;
        }
    }

    internal struct ComponentBlockIterator
    {
        private int _version;
        private int _remaining;
        private int _currentBlock;
        private int _currentBlockOffset;
        private unsafe ComponentDataBuffer* _buffer;

        internal unsafe ComponentBlockIterator(ComponentDataBuffer* buffer)
        {
            this._buffer = buffer;
            this._version = buffer->Version;
            this._currentBlock = 0;
            this._currentBlockOffset = 1;
            this._remaining = buffer->Count;
        }

        internal unsafe ComponentBlockIterator(ComponentDataBuffer* buffer, int offset, int count)
        {
            this._buffer = buffer;
            this._version = buffer->Version;
            this._currentBlock = 0;
            this._currentBlockOffset = 0;
            this._remaining = 0;
            this.Reset(offset, count);
        }

        public unsafe void Reset()
        {
            this._currentBlock = 0;
            this._currentBlockOffset = 1;
            this._remaining = this._buffer->Count;
        }

        public unsafe void Reset(int offset, int count)
        {
            ++offset;
            if (offset > this._buffer->_count)
                offset = this._buffer->_count;
            this._currentBlock = offset / this._buffer->_blockCapacity;
            this._currentBlockOffset = offset % this._buffer->_blockCapacity;
            this._remaining = FPMath.Clamp(count, 0, this._buffer->_count - offset);
        }

        internal unsafe void ValidateIterator()
        {
            if (this._version != this._buffer->Version)
                throw new InvalidOperationException(string.Format(
                    "Can't remove components of a type while iterating over the same type : {0}",
                    (object)ComponentTypeId.GetComponentType(this._buffer->_componentTypeID)));
        }

        public unsafe bool NextBlock(out byte* components, out EntityRef* entities, out int count)
        {
            this.ValidateIterator();
            while (this._currentBlock < this._buffer->BlockListCount && this._remaining > 0)
            {
                count = this._buffer->_blockCapacity - this._currentBlockOffset;
                Assert.Check(count >= 0);
                if (count > 0)
                {
                    count = Math.Min(this._remaining, count);
                    entities = this._buffer->GetBlockEntityRefs(this._currentBlock);
                    components = this._buffer->GetBlockData(this._currentBlock);
                    entities += this._currentBlockOffset;
                    components += this._currentBlockOffset * this._buffer->_stride;
                    this._remaining -= count;
                    this._currentBlockOffset += count;
                    return true;
                }

                ++this._currentBlock;
                this._currentBlockOffset = 0;
            }

            components = (byte*)null;
            entities = (EntityRef*)null;
            count = 0;
            return false;
        }
    }
}